#ifndef     __MDB_UART_H
#define     __MDB_UART_H

#include "../rtos/event_flags.h"
#include "../utils/error.h"
#include "../rtos/message_queue.h"
#include "../serialize/encode.h"
#include "../serialize/decode.h"

namespace driver {

struct Uint16View {
    const uint16_t *buf;
    uint8_t len;
    
    Uint16View() : buf(nullptr), len(0) {}
    Uint16View(const uint16_t *b, uint8_t l) : buf(b), len(l) {}
};

struct MdbResp {

    static constexpr uint8_t ACK = 0x00;
    static constexpr uint8_t RET = 0xAA;
    static constexpr uint8_t NAK = 0xFF;

    const uint8_t *buf;
    uint8_t len;

    template <typename... Ts>
    utils::Error parse(Ts&... ts) {
        return serialize::decode_all(buf, len - 1, ts...);
    }

    bool is_ack() const { return (len == 1) && (buf[0] == ACK); }
};

class MdbUart : private utils::NoCopyable {

public:
    static constexpr uint32_t RECV_BUF_SIZE = 36;
    static constexpr uint32_t WRITE_BUF_SIZE = 36;

    static constexpr uint32_t MDB_BYTE_MS = 10;
    static constexpr uint32_t MDB_RESP_MS = 50;

    static constexpr uint32_t FLAG_DMA_TC = 0x01;
    
    void init();
    
    template <typename... Ts>
    utils::Error request(MdbResp &resp, uint8_t cmd, const Ts&... ts) {
        write(cmd, ts...);
        return receive(resp);
    }

    void on_recv_idle();
    void on_dma_tc() { m_flags.set_flags(FLAG_DMA_TC); }
private:
    static void write_by_dma(const void *buf, uint16_t len);
    utils::Error receive(MdbResp &resp) {
        uint8_t err_count = 0;
        utils::Error err;
        for (;;) {
            err = receive_once(resp);
            if ((err.err_code == utils::ec::MDB_CHECK_SUM_FAIL) && (err_count <= 3)) {
                write_no_check(MdbResp::RET);
                resp.len = 0;
                err_count += 1;
            } else {
                return err;
            }
        }
    }
    utils::Error receive_once(MdbResp &resp);

    bool take(uint16_t& v) {
        if (m_recv_slice.len <= 0) {
            if (osOK != m_mq.poll(m_recv_slice, MDB_BYTE_MS)) {
                return false;
            }
        }
        v = m_recv_slice.buf[0];
        m_recv_slice.buf += 1;
        m_recv_slice.len -= 1;
        return true;
    }

    template <typename... Ts>
    void write(uint8_t cmd, const Ts&... ts) {
        m_flags.wait_any_flags(FLAG_DMA_TC);
        uint8_t len = serialize::encode_all(m_byte_write_buf, cmd, ts...);
        uint8_t s = 0;
        for (uint8_t i = 0; i < len; i ++) {
            m_write_buf[i] = m_byte_write_buf[i];
            s += m_byte_write_buf[i];
        }
        m_write_buf[0] |= 0x100;
        m_write_buf[len] = s;
        write_by_dma(m_write_buf, len + 1);
    }

    void write_no_check(uint8_t cmd) {
        m_flags.wait_any_flags(FLAG_DMA_TC);
        m_write_buf[0] = cmd;
        write_by_dma(m_write_buf, 1);
    }

    Uint16View m_recv_slice;
    uint8_t m_byte_recv_buf[RECV_BUF_SIZE];
    uint8_t m_byte_write_buf[WRITE_BUF_SIZE];

    uint16_t m_recv_buf[RECV_BUF_SIZE];
    uint16_t m_write_buf[WRITE_BUF_SIZE];
    volatile uint32_t m_recv_index = 0;
    rtos::MessageQueue<Uint16View, 2> m_mq;
    rtos::EventFlags m_flags;
};

extern MdbUart mdbUart;

}




#endif
